package cn.com.bluemoon.daps.common.datascope.interceptor;

import cn.com.bluemoon.daps.common.datascope.BmVPN;
import cn.com.bluemoon.daps.common.datascope.config.BmVPNProperties;
import cn.com.bluemoon.daps.common.domain.ResultBean;
import cn.com.bluemoon.daps.common.exception.DapThrowException;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * web 网络拦截处理
 * <p>
 * 注：只支持直连代理（多次代理取最后一个代理ip）Nginx的ip拦截，不支持服务之间调用的ip拦截，
 * <p>
 * 若要支持，目前需要下游梳理上游的ip，配置到{@link BmVPN#openIPAddress()}上
 *
 * @author Jarod.Kong
 */
@Slf4j
public class WebNetworkRequestInterceptor extends HandlerInterceptorAdapter {

    private final BmVPNProperties bmVPNProperties;

    public WebNetworkRequestInterceptor(BmVPNProperties bmVPNProperties) {
        this.bmVPNProperties = bmVPNProperties;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            BmVPN bmVPN = AnnotationUtils.findAnnotation(((HandlerMethod) handler).getMethod(), BmVPN.class);
            if (bmVPN == null || !bmVPN.enabled()) {
                log.debug("bm VPN pass !!");
                return true;
            }
            // 配置了BmVPN
            //是否有放行ip
            String[] ipAddress = bmVPN.openIPAddress();
            if (ipAddress.length == 0 && bmVPNProperties.getOpenIpAddress().isEmpty()) {
                log.warn("接口uri：{}，未配置（全局和接口）接口放行ip地址，拒绝所有请求", request.getRequestURI());
                throw new DapThrowException("接口拒绝所有网络访问", ResultBean.FAIL_CODE);
            }
            // 获取remote-ip
            String clientIP = ServletUtil.getClientIP(request);
            log.debug("接口uri：{}，来源客户端ip：{}", request.getRequestURI(), clientIP);
            if (Validator.isIpv4(clientIP) && !NetUtil.isInnerIP(clientIP)) {
                log.warn("注：网络拦截发现来源ip：{}，可能是非内网客户端", clientIP);
            }
            if (clientIP == null) {
                throw new DapThrowException("未知客户端来源，拒绝访问", ResultBean.FORBIDDEN.getCode());
            }
            final String proxyIP = getWebProxyIp(request);
            // 整合全局配置和接口配置openIp
            Set<String> openIP = new HashSet<>(16);
            openIP.addAll(bmVPNProperties.getOpenIpAddress());
            openIP.addAll(Arrays.asList(ipAddress));
            // 判断是否符合放行的ip
            if (!openIP.contains(proxyIP)) {
                log.info("接口uri：{}，来源客户端ip：{}，代理ip：{}，不放行", request.getRequestURI(), clientIP, proxyIP);
                throw new DapThrowException(bmVPN.rejectMsg(), ResultBean.FORBIDDEN.getCode());
            }
            log.info("接口uri：{}，已知来源客户端ip：{}，经过代理ip：{}请求，网络正常访问", request.getRequestURI(), clientIP, proxyIP);
        }
        return true;
    }

    private String getWebProxyIp(HttpServletRequest request) {
        String remoteAddr = request.getRemoteAddr();
        log.debug("请求（直连）代理ip：{}", remoteAddr);
        return getLastProxyIp(remoteAddr);
    }

    /**
     * 从多级反向代理中获得最后一个非unknown IP地址
     *
     * @param ip 获得的IP地址
     * @return 最后一个非unknown IP地址
     */
    private String getLastProxyIp(String ip) {
        // 多级反向代理检测
        if (ip != null && ip.indexOf(",") > 0) {
            final String[] ips = ip.trim().split(",");
            for (int i = ips.length - 1; i >= 0; i--) {
                String subIp = ips[i];
                if (!isUnknown(subIp)) {
                    ip = subIp;
                    break;
                }
            }
        }
        return ip;
    }

    private boolean isUnknown(String checkString) {
        return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
    }

}